use std::io::Read;

use parser::{Block, Expr, Source, Value};
use runtime::Env;

const ZERO_C: char = '\0';
const ARGV: &str = "argv";
const FUNC: &str = "func";

mod builtin;
mod parser;
mod runtime;

fn main() {
    if runtime().is_err() {
        std::process::exit(1)
    }
}

fn runtime() -> Result<(), ()> {
    let argv: Vec<_> = std::env::args().skip(1).collect();

    let Some((script, script_argv)) = argv.split_first() else {
        eprintln!("Usage: quansh SCRIPT ARGS...");
        return Err(());
    };

    let source = if script == "-" {
        let mut source = String::new();
        std::io::stdin()
            .read_to_string(&mut source)
            .map_err(|err| eprintln!("failed to source from stdin: {err}"))?;
        source
    } else {
        std::fs::read_to_string(script)
            .map_err(|err| eprintln!("failed to open source file: {err}"))?
    };

    let source = Source::new(source.chars().collect());

    let mut parser = parser::Parser::new(source)?;
    let mut env_func = builtin::new_stdlib();

    while let Block::Func(func) = parser.parse()? {
        env_func.bind_func(func.name.to_owned(), func);
    }

    let main_func = env_func.take_main_func()?;

    let env = Env::new(None);

    let argv: Vec<_> = script_argv
        .iter()
        .map(|s| Expr::Value(Value::String(s.to_owned())))
        .collect();

    let main_exit = env.eval_func_call(&env_func, &main_func, &argv)?;

    let exit = match main_exit {
        Value::Bool(b) => {
            if b {
                0
            } else {
                1
            }
        }
        Value::Integer(n) => n as i32,
        _ => {
            eprintln!("invalid return type of main function: {main_exit:?}");
            1
        }
    };

    std::process::exit(exit)
}
